var logger = require("../utils/log");

var socketMgr = require("./socket_service");
var roomMgr = require("./roommgr");
var mjUtils = require("./bbmjUtils");
var redis = require("../utils/redis");

var games = {};
var gamesSeats = {};

// const PERIOD_READY_0 = 0; //准备
// const PERIOD_HOLD_1 = 1; //发牌
// const PERIOD_GAME_2 = 2; //游戏阶段
// const PERIOD_SCORE_3 = 3; //计分阶段
// const PERIOD_IDLE_4 = 4; //空闲阶段

const PERIOD_READY = 0; //准备
const PERIOD_GAMING = 1; //游戏中
const PERIOD_END = 2; //游戏结束
// const PERIOD_DISSOLVING = 999; //正在请求解散

const MJ_GUO_ACTION = 0; //过动作
const MJ_PNEG_ACTION = 1; //碰动作
const MJ_GANG_DIAN_ACTION = 2; //点杠动作
const MJ_GANG_BU_ACTION = 3; //补杠动作
const MJ_GANG_AN_ACTION = 4; //暗杠动作
const MJ_CHI_ACTION = 5; //吃牌动作
const MJ_HU_ACTION = 6; //胡牌动作

const MJ_REMAIN_COUNT = 14; //蚌埠麻将最后遗留14张牌 流局

var times = {};

//初始化数据
function initGame(room) {
    //牌局共享的数据 包括状态 顺序 出牌
    var game = {
        room:room,
        numOfGames:0,
        type:room.conf.type,
        roomNo:room.roomNo,
        //如果是怀远麻将  要算上花+庄家的风
        //连庄的情况下 输赢翻倍
        //上局是谁赢了
        winTurn:-1,
        //连赢的次数
        winCount:0,
    };
    game.plays = room.conf.renshu;//玩家数量 默认是4 也可以是3
    game.gameSeats = new Array(game.plays);//座位信息
    game.memberOfReady = 0;//准备的人数
    //牌的长度 蚌埠麻将144张字 108 + 28 + 8
    if (room.conf.hua == 1) { //怀远麻将带花
        game.paiLength = 144;
    } else {
        game.paiLength = 136;
    }

    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i] = {};
        seat.game = game;
        seat.seatIndex = i;//0 1 2 3
    }

    games[room.roomNo] = game;
    // refreshGame(game);

    return game;
};

function refreshGame(game) {
    game.memberOfReady = 0;//准备的人数
    game.memberOfAgree = 0;//同意的人数
    game.memberOfRefuse = 0;//拒绝的人数

    game.majongs = null;//麻将牌
    game.state = PERIOD_READY;//0等待游戏开始 1游戏进行中  999正在请求解散
    if (game.winTurn > -1) {
        game.turn = game.winTurn;//当前轮到谁
    } else {
        game.turn = 0;//当前轮到谁
    }
    game.lastTurn = 0; //上次出牌的玩家 用于判定杠上开花
    game.lastGangPai = 0; //上次做出的动作 用于判定抢杠胡

    game.moPai = -1;//摸的牌
    game.chuPai = -1;//出的牌
    game.index = 0;//牌的下标

    game.mayPeng = null;//可以进行碰杠的用户 同时只有一个碰杠
    game.mayChi = null;//可以进行吃的用户 同时只有一个吃
    game.mayHuArr = null;//可能的动作 2个胡牌 或者 3个胡牌
    game.mayActionLength = 0;
    game.didActionArr = null;//用户做出的动作 1个碰 2个胡牌 或者 3个胡牌 (最多只有一个碰)

    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];

        seat.ready = false;
        //持有的牌
        seat.holds = [];
        //打出的牌
        seat.folds = [];
        //暗杠的牌
        seat.angangs = [];
        //点杠的牌(别人打直接杠)
        seat.diangangs = [];
        //补杠的牌(碰了之后再摸到牌杠)
        seat.bugangs = [];
        //碰了的牌
        seat.pengs = [];
        //吃了的牌
        seat.chis = [];
        //摸到的花 摸到立即打出去
        seat.huas = [];

        //玩家手上的牌的数目，用于快速判定碰杠
        seat.countMap = {};
        //玩家听牌，用于快速判定胡了的番数 每次算牌都会新建{}
        seat.tingMap = null;

        //是否可以杠
        seat.canGang = false;
        //暗杠 补杠可能有多张 每次杠牌都会新建[] 专门用来表示 补杠和暗杠的合集
        seat.gangPais = null;
        //是否可以碰
        seat.canPeng = false;
        //是否可以吃
        seat.canChi = false;
        //1 表示 12吃3  2表示13吃2  3表示23吃1
        seat.chiTypes = null;
        //是否可以胡
        seat.canHu = false;
        //是否可以出牌
        seat.canChuPai = false;
        //是否过牌
        seat.didAction = -1;

        seat.score = 0;

        //上次过胡 需要过一轮才能输
        seat.lastGuoHu = -1;

        seat.dissoveOperate = -1; // 1代表同意 2代表拒绝
    }

    game.numOfGames++;
};

function xiPai(game) {
    var pkPais = new Array(game.paiLength); //蚌埠麻将带花 200张

    var index = 0;

    //万 1 ~ 9表示万
    for(var i = 1; i < 10; ++i){
        for(var j = 0; j < 4; ++j){
            pkPais[index] = i;
            index++;
        }
    }

    //条 11 ~ 19 表示条子
    for(var i = 11; i < 20; ++i){
        for(var j = 0; j < 4; ++j){
            pkPais[index] = i;
            index++;
        }
    }

    //筒 21 ~ 29 表示筒子
    for(var i = 21; i < 30; ++i){
        for(var c = 0; c < 4; ++c){
            pkPais[index] = i;
            index++;
        }
    }

    //东南西北中发白 31 ~ 37
    for(var i = 31; i < 38; ++i){
        for(var c = 0; c < 4; ++c){
            pkPais[index] = i;
            index++;
        }
    }

    if (game.paiLength == 144) {
        //梅兰竹菊春夏秋冬 41 ~ 48
        for(var i = 41; i < 49; ++i){
            pkPais[index] = i;
            index++;
        }
    }

    //乱序
    var length = game.paiLength - 1;
    for (var i = 0; i < length; i++) {
        var lastIndex = length-i;
        var temp = Math.floor(Math.random() * lastIndex);
        var t = pkPais[temp];
        pkPais[temp] = pkPais[lastIndex];
        pkPais[lastIndex] = t;
    }

    game.majongs = pkPais;
};

function faPai(game) {
    game.index = 0;

    //每人13张 一共 13*4 ＝ 52张 庄家多一张 53张
    var length = game.plays*13
    for (var i = 0; i < length; ++i){ //2 3 0 1
        moPai(game);
        moveToNextUser(game);
    }

    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];
        seat.holds.sort(function (a, b) {
            return a - b;
        });
    }
    socketMgr.notify_user_majongs(game);

    for (var i = 0; i < game.plays; ++i) {
        var seat = game.gameSeats[i];
        //自己只能自摸 或者 暗杠
        //别人只能碰 杠 胡
        checkCanTingPai(seat);
    }
    //庄家摸一张
    toMoPai(game);
    game.state = PERIOD_GAMING;
};

function moPai(game) {
    if ((game.index + MJ_REMAIN_COUNT)== game.paiLength ) return -1;

    logger.info('mopai turn', game.turn);
    var seat = game.gameSeats[game.turn];
    var pai = game.majongs[game.index];
    seat.holds.push(pai);

    //统计牌的数目 ，用于快速判定（空间换时间）
    var c = seat.countMap[pai];
    if (c == null) {
        c = 1;
    } else {
        c++;
    }
    seat.countMap[pai] = c;
    game.index++;

    return pai;
};

function chuPai(userId, pai){
    var seat = gamesSeats[userId];
    if (seat == null) {
        logger.error('chupai turn error', pai);
        return;
    }

    var game = seat.game;
    //1，如果不该他出 则忽略  2，如果还有动作执行 则忽略
    if (game.turn != seat.seatIndex || hasOperations(seat)) return;

    pai = parseInt(pai);
    //从此人牌中扣除
    var index = seat.holds.indexOf(pai);
    if (index == -1) return;

    seat.holds.splice(index,1);
    game.chuPai = pai;
    game.moPai = -1;
    if (seat.countMap[pai] == 1) {
        delete seat.countMap[pai];
    } else {
        seat.countMap[pai]--;
    }

    var isHua = isHuaPai(pai);
    logger.info('isHua', isHua);
    logger.info('chupai turn', game.turn);
    if (isHua) {
        //如果出的是花牌 摸牌轮子还是自己
        socketMgr.notify_all_chupai(game, 1);
        seat.huas.push(game.chuPai);
        setTimeout(function(){
            toMoPai(game);
        }, 1000);
        return;
    }

    socketMgr.notify_all_chupai(game, 0);
    checkCanTingPai(seat);

    var mayPeng = null;
    var mayChi = null;
    var mayHuArr = [];
    var length = 0;
    for (var i = 0; i < game.plays; ++i){
        //玩家自己不检查
        logger.info('--------game.turn', game.turn);
        if (game.turn == i) continue;

        var tempSeat = game.gameSeats[i];
        checkUserOperation(tempSeat);
        var canPeng = tempSeat.canPeng || tempSeat.canGang;
        if (canPeng) mayPeng = tempSeat.userId;
        var canChi = tempSeat.canChi;
        if (canChi) mayChi = tempSeat.userId;

        var canHu = tempSeat.canHu;
        if (canHu) mayHuArr.push(tempSeat.userId);

        if (canChi || canPeng || canHu) length++;
    }
    game.mayChi = mayChi;
    game.mayPeng = mayPeng;
    game.mayHuArr = mayHuArr;
    game.mayActionLength = length;
    logger.debug('length', length);

    //如果没有人有操作，则向下一家发牌，并通知他出牌
    if(length < 1){
        seat.folds.push(game.chuPai);
        setTimeout(function(){
            moveToNextUser(game);
            toMoPai(game);
        }, 1000);
    }
};

function moveToNextUser(game){
    game.lastTurn = game.turn;
    game.turn++;
    game.turn %= game.plays;
    logger.debug('moveToNextUser game turn', game.turn);
    return game.gameSeats[game.turn];
};

function moveToUser(game, index){
    game.lastTurn = game.turn;
    game.turn = index;
    game.turn %= game.plays;
    logger.debug('moveToUser game turn', game.turn);
    return game.gameSeats[game.turn];
};

function toMoPai(game){
    var seat = game.gameSeats[game.turn];
    var pai = moPai(game, game.turn);
    //牌摸完了，结束
    if(pai == -1){
        willEndGame(game, false);
        return -1;
    }
    seat.holds.sort(function (a, b) {
        return a - b;
    });
    game.moPai = pai;
    game.chuPai = -1;
    socketMgr.notify_user_mopai(seat, pai);

    checkUserOperation(seat);
};

//检查听牌
function checkCanTingPai(seat){
    //每次检查听牌的就把之前的听牌清空
    // 清一色20分
    // 混一色（12张一种类型的序牌+一对字牌）10分
    // 七对（7对 对子）10分
    // 对对胡（4个刻字 1对对子）10分
    seat.tingMap = {};

    var fanshu = 0;
    //检查清一色
    var qingyise = isQingYiSe(seat);
    var hunyise = false;
    if (qingyise) { //10番
        fanshu = 20;
    } else {
        //检查混一色
        hunyise = isHunYiSe(seat);
        if (hunyise) {
            fanshu = 10;
        }
    }
    if (qingyise || hunyise) {
        var type = getMJType(seat.holds[0])
        if (type == 0) {
            mjUtils.checkPingHu(seat, 1, 9, qingyise);
        } else if (type == 1) {
            mjUtils.checkPingHu(seat, 11, 19, qingyise);
        } else if (type == 2) {
            mjUtils.checkPingHu(seat, 21, 29, qingyise);
        }
        for (var pai in seat.tingMap) {
            seat.tingMap[pai] = fanshu;
        }
        logger.debug('--------qingyise || hunyise');
    }

    var sdui= is7Dui(seat);//检查7对
    var duiduiHu = false;
    if (sdui != -1) {
        fanshu += 10;
        seat.tingMap[sdui] = fanshu;
        logger.debug('--------is7Dui');
    } else {
        duiduiHu = checkDuiduiHu(seat);//检查对对胡
        if (duiduiHu.length > 0) { //对对胡也可能平胡
            logger.debug('--------duiduiHu');
            fanshu += 10;
            duiduiHu.forEach(function (pai, index) {
                seat.tingMap[pai] = fanshu;
            });
            //检查对对胡的平胡
            mjUtils.checkPingHu(seat, 1, 9);
            mjUtils.checkPingHu(seat, 11, 19);
            mjUtils.checkPingHu(seat, 21, 29);
            logger.debug('--------duiduiHu');
        }
    }

    //还没有胡字
    if (!qingyise && !hunyise && sdui == -1 && duiduiHu.length < 1 && Object.keys(seat.tingMap).length < 1) {
        logger.debug('checkPingHu begin', Date.now());
        mjUtils.checkPingHu(seat, 1, 9);
        mjUtils.checkPingHu(seat, 11, 19);
        mjUtils.checkPingHu(seat, 21, 29);
        logger.debug('checkPingHu end', Date.now());
    }

    //如果听牌 则有seat.tingMap有值 如果没听牌 则是空值 有可能听牌打成不听牌
    socketMgr.notify_user_tingpais(seat);
};

function isQingYiSe(seat){
    var type = getMJType(seat.holds[0]);

    if (!isSameType(type, seat.chis)) {
        return false;
    }
    if(!isSameType(type, seat.pengs)){
        return false;
    }
    if(!isSameType(type, seat.angangs)){
        return false;
    }
    if(!isSameType(type, seat.bugangs)){
        return false;
    }
    if(!isSameType(type, seat.diangangs)){
        return false;
    }
    //检查手上的牌
    if(!isSameType(type, seat.holds)){
        return false;
    }
    return true;
};

function isHunYiSe(seat){
    var type = getMJType(seat.holds[0]);

    if (!isSameType(type, seat.chis)) {
        return false;
    }
    if(!isSameType(type, seat.pengs)){
        return false;
    }
    if(!isSameType(type, seat.angangs)){
        return false;
    }
    if(!isSameType(type, seat.bugangs)){
        return false;
    }
    if(!isSameType(type, seat.diangangs)){
        return false;
    }

    var tempZiPaiHold = [];
    var tempXuPaiHold = [];
    seat.holds.forEach(function (pai, index) {
        if (isZiPai(pai)) {
            tempZiPaiHold.push(pai);
        } else {
            tempXuPaiHold.push(pai);
        }
    });
    //混一色一对字牌做将
    if (tempXuPaiHold.length == 2) {
        if (tempZiPaiHold[0] == tempZiPaiHold[1] && isSameType(type, tempXuPaiHold)) {
            return true;
        }
    }
    return false;
};

function is7Dui(seat) {
    var ls = Object.keys(seat.countMap).length;
    if (ls == 7) {
        var duiS = 0;
        var dan = -1;
        for (var pai in seat.countMap) {
            var count = seat.countMap[pai];
            if (count == 2) {
                duiS++;
            } else {
                dan = pai;
            }
        }
        if (duiS == 6) {
            return dan;
        }
        return -1;
    }
    return -1;
};

function isZiPai(pai){
    return pai>30 && pai<40;
};

function isHuaPai(pai){
    return pai>40 && pai<50;
};

function isSameType(type, arr){
    var length = arr.length;
    for(var i = 0; i < length; ++i){
        var temp = getMJType(arr[i]);
        if(temp != type){
            return false;
        }
    }
    return true;
};

function isMenQing(seat){
    return (seat.chis.length + seat.pengs.length + seat.bugangs.length + seat.diangangs.length) == 0;
};

function checkDuiduiHu(seat) {
    //检查是否是对对胡  只需要检查手上的牌
    //对对胡叫牌有两种情况
    //1、N坎 + 1张单牌
    //2、N-1坎 + 两对牌
    //可以放听牌的字段
    var arr = [];
    //吃过牌 就不是对对胡了
    if (seat.chis.length > 0) return arr;

    var singleCount = 0;
    var pairCount = 0;
    var colCount = 0;
    for (var i in seat.countMap) {
        var c = seat.countMap[i];
        if (c == 1){
            singleCount++;
            arr.push(i);
        } else if(c == 2){
            pairCount++;
            arr.push(i);
        } else if(c == 3) {
            colCount++;
        } else if(c == 4) {//手上有4个一样的牌，当成两对
            pairCount +=2;
        }
    }
    //区分对对胡 和 单吊
    if ((pairCount == 2 && singleCount == 0) || (pairCount == 0 && singleCount == 1)) {
        return arr;
    }
    return new Array(0);
};

//检查用户可能的操作
function checkUserOperation(seat) {
    var game = seat.game;

    logger.debug("--------checkUserOperation", seat.seatIndex);
    seat.gangPais = [];

    if (seat.seatIndex == game.turn) { //是自己的顺序 判断暗杠 自摸
        logger.debug("--------checkMyOperation");
        checkCanBuGang(seat);
        checkCanAnGang(seat);
        seat.canGang = seat.gangPais.length > 0;

        //检查胡
        checkCanHu(seat);
    } else { //判断碰杠胡
        logger.debug("--------checkOtherOperation");
        var can = checkCanDianGang(seat);
        if (can) {
            seat.canPeng = true;
        } else {
            checkCanPeng(seat);
        }
        checkCanChi(seat);
        //检查胡
        if (game.plays == 3) { //三人麻将 只能自摸
            checkCanHu(seat);
        }
    }
    if(hasOperations(seat)){
        seat.didAction = -1;
        sendOperations(seat);
    }
};

//检查是否可以暗杠
function checkCanAnGang(seat){
    // seat.canGang = false;

    var game = seat.game;
    //如果没有牌了，则不能再杠
    if(game.index == game.paiLength) return false;

    for(var key in seat.countMap){
        var c = seat.countMap[key];
        if(c == 4){
            // seat.canGang = true;
            seat.gangPais.push(key);
        }
    }
    // return seat.canGang;
};

//检查是否可以补杠(碰了之后再摸到牌杠)
function checkCanBuGang(seat){
    // seat.canGang = false;
    var game = seat.game;
    //如果没有牌了，则不能再杠
    if(game.index == game.paiLength)return false;

    //从碰过的牌中选
    var length = seat.pengs.length;
    for(var i = 0; i < length; ++i){
        var pai = seat.pengs[i];//碰过
        if(seat.countMap[pai] == 1){ //手牌还有一张
            // seat.canGang = true;
            seat.gangPais.push(pai);
        }
    }
    // return seat.canGang;
};

//检查是否可以点杠(别人打直接杠)
function checkCanDianGang(seat){
    seat.canGang = false;
    var game = seat.game;
    //如果没有牌了，则不能再杠
    if(game.index == game.paiLength) return false;

    var count = seat.countMap[game.chuPai];
    if(count == 3){
        seat.canGang = true;
        seat.gangPais.push(game.chuPai);
    }
    return seat.canGang;
};

//检查是否可以碰
function checkCanPeng(seat) {
    var game = seat.game;
    seat.canPeng = false;
    var count = seat.countMap[game.chuPai];
    if(count == 2){
        seat.canPeng = true;
    }
    return seat.canPeng;
};

//检查是否可以碰
function checkCanChi(seat) {
    var game = seat.game;
    var nextS = (game.turn +1)%game.plays;
    if (seat.seatIndex != nextS) return;

    seat.chiTypes = [];
    seat.canChi = false;
    if (game.chuPai > 0 && game.chuPai < 30) {
        //1 2吃3
        if (seat.countMap[game.chuPai-2] > 0 && seat.countMap[game.chuPai-1] > 0) {
            seat.canChi = true;
            seat.chiTypes.push(1);
        }
        //1 3吃2
        if (seat.countMap[game.chuPai+1] > 0 && seat.countMap[game.chuPai-1] > 0){
            seat.canChi = true;
            seat.chiTypes.push(2);
        }
        //2 3吃1
        if (seat.countMap[game.chuPai+2] > 0 && seat.countMap[game.chuPai+1] > 0){
            seat.canChi = true;
            seat.chiTypes.push(3);
        }
        logger.debug('chiType', seat.chiTypes);
    }
    return seat.canChi;
};

//检查是否可以胡
function checkCanHu(seat) {
    var game = seat.game;
    seat.canHu = false;
    if (seat.seatIndex == game.turn) { //是自己的顺序 自摸
        if (seat.tingMap[game.moPai]) {
            seat.canHu = true;
        }
    } else {
        if (seat.tingMap[game.chuPai]) {
            seat.canHu = true;
        }
    }
    return seat.canHu;
};

function hasOperations(seat) {
    if(seat.canGang || seat.canPeng || seat.canHu || seat.canChi) return true;
    return false;
};

function sendOperations(seat) {
    var data = {
        hu:seat.canHu,
        peng:seat.canPeng,
        gang:seat.canGang,
        gangPais:seat.gangPais,
        chi:seat.canChi,
        chiTypes:seat.chiTypes,
    };
    socketMgr.notify_user_operate_available(seat, data);
};

function chi(userId, type) {
    var seat = gamesSeats[userId];
    if(seat == null || !seat.canChi) return;

    var game = seat.game;

    seat.chiTypes = type;
    logger.debug('chiTypes', seat.chiTypes);
    //不能胡 只能碰杠
    if (game.mayActionLength == 1) {
        doChi(seat);
        return;
    }

    if (game.didActionArr == null) game.didActionArr = [];
    game.didActionArr.push(seat.userId);
    seat.didAction = MJ_CHI_ACTION;

    //所有人都做了动作
    if (game.didActionArr.length == game.mayActionLength) {
        doAction(game);
    }
};

function doChi(seat, pai) {
    var game = seat.game;
    clearAllOptions(game);

    if (pai == null) pai = game.chuPai;

    var pai1 = -1;
    var pai2 = -1;
    if (seat.chiTypes == 1) {//12吃3
        pai1 = pai-2;
        pai2 = pai-1;
    }if (seat.chiTypes == 2) {//13吃2
        pai1 = pai-1;
        pai2 = pai+1;
    }if (seat.chiTypes == 3) {//23吃1
        pai1 = pai+2;
        pai2 = pai+1;
    }
    spliceHolds(seat, pai1);
    spliceHolds(seat, pai2);

    seat.chis.push(pai);

    socketMgr.notify_all_user_operate(seat, MJ_CHI_ACTION, pai, seat.chiTypes);

    moveToUser(game, seat.seatIndex);
    //碰了之后 只能出牌或者暗杠,补杠 出牌时会做听牌检查
    checkUserOperation(seat);
};

function peng(userId) {
    var seat = gamesSeats[userId];
    if(seat == null || !seat.canPeng) return;

    var game = seat.game;
    //不能胡 只能碰杠
    if (game.mayActionLength == 1) {
        doPeng(seat);
        return;
    } else if (game.mayActionLength == 2) {
        if (game.mayPeng && game.mayChi) {
            doPeng(seat);
            return;
        }
    }

    if (game.didActionArr == null) game.didActionArr = [];
    game.didActionArr.push(seat.userId);
    seat.didAction = MJ_PNEG_ACTION;

    //所有人都做了动作
    if (game.didActionArr.length == game.mayActionLength) {
        doAction(game);
    }
};

function doPeng(seat) {
    var game = seat.game;
    clearAllOptions(game);

    var pai = game.chuPai;
    var c = seat.countMap[pai];
    if(c < 2) return;

    for(var i = 0; i < 2; ++i){
        spliceHolds(seat, pai);
    }

    seat.pengs.push(pai);
    socketMgr.notify_all_user_operate(seat, MJ_PNEG_ACTION, pai);

    moveToUser(game, seat.seatIndex);
    //碰了之后 只能出牌或者暗杠,补杠 出牌时会做听牌检查
    checkUserOperation(seat);
};

function gang(userId, pai) {
    var seat = gamesSeats[userId];
    if(seat == null || !seat.canGang) return;

    var game = seat.game;

    seat.gangPais = pai;
    //是自己摸牌 那就是补杠 或者 暗杠
    if (game.turn == seat.seatIndex) {
        doGang(seat, pai);
        return;
    }
    //只存在一个动作
    if (game.mayActionLength == 1) {
        doGang(seat);
        return;
    } else if (game.mayActionLength == 2) {
        if (game.mayPeng && game.mayChi) {
            doGang(seat);
            return;
        }
    }

    if (game.didActionArr == null) game.didActionArr = [];
    game.didActionArr.push(seat.userId);
    seat.didAction = MJ_GANG_DIAN_ACTION;

    //所有人都做了动作
    if (game.didActionArr.length == game.mayActionLength) {
        doAction(game);
    }
};

function doGang(seat, pai) {
    var game = seat.game;
    clearAllOptions(game);

    if (pai == null) pai = seat.gangPais;

    game.lastGangPai = 0;
    var type = -1;
    //只能有一个人碰杠
    if (game.turn == seat.seatIndex) { //是自己的轮 那就是补杠 或者 暗杠
        if (seat.countMap[pai] == 4) { //暗杠
            for(var i = 0; i < 4; ++i){
                spliceHolds(seat, pai);
            }
            seat.angangs.push(pai);
            type = MJ_GANG_AN_ACTION;
        } else if (seat.countMap[pai] == 1) { //补杠
            spliceHolds(seat, pai);

            seat.bugangs.push(pai);
            type = MJ_GANG_BU_ACTION;

            game.lastGangPai = pai;
        }
    } else { //是点杠
        for(var i = 0; i < 3; ++i){
            spliceHolds(seat, pai);
        }
        seat.diangangs.push(pai);
        type = MJ_GANG_DIAN_ACTION;
    }
    socketMgr.notify_all_user_operate(seat, type, pai);

    if (game.lastGangPai) {
        logger.debug('检查抢杠胡');
        var tempChupai = game.chuPai;
        game.chuPai = game.lastGangPai;
        for (var i = 0; i < game.plays; ++i){
            if (game.turn == i) continue;

            var tempSeat = game.gameSeats[i];
            checkCanHu(tempSeat);
            if(hasOperations(seat)){
                seat.didAction = -1;
                sendOperations(seat);
            }
        }
        game.chuPai = tempChupai;
    }

    //去做听牌检查
    checkCanTingPai(seat);

    //杠了之后 只能去摸牌
    moveToUser(game, seat.seatIndex);
    toMoPai(game);
};

function hu(userId, pai) {
    var seat = gamesSeats[userId];
    if(seat == null || !seat.canHu) return;

    var game = seat.game;
    //是自己摸牌 且能胡 那就是自摸
    if (game.turn == seat.seatIndex) {
        doHu(seat, pai);
        return;
    }
    //只有一家能胡
    if (game.mayHuArr.length == 1) {
        doHu(seat);
        return;
    } else { //有多家胡 如果是下家 则马上胡牌
        var nextPlay = (game.turn+1)%game.plays;
        if (nextPlay == seat.seatIndex) {
            doHu(seat);
            return;
        }
    }

    if (game.didActionArr == null) game.didActionArr = [];
    game.didActionArr.push(seat.userId);
    seat.didAction = MJ_HU_ACTION;

    //所有人都做了动作
    if (game.didActionArr.length == game.mayActionLength) {
        doAction(game);
    }
};

function doHu(seat, pai) {
    logger.debug('dohu', seat.seatIndex);
    if (seat == null) return;

    var game = seat.game;
    if (pai == null) pai = game.chuPai;
    logger.debug('dohu', pai);

    if (game.winTurn == seat.seatIndex) {
        game.winCount++;
    } else {
        game.winTurn = seat.seatIndex;
        game.winCount = 1;
    }

    var huData = {
        userId:seat.userId,
        pai:pai,
        fan:seat.tingMap[pai],
    };
    var isZimo = game.turn == seat.seatIndex;
    huData.isZimo = isZimo;

    if (isZimo) {
        huData.isGangShangHu = game.lastTurn == seat.seatIndex;
    } else {
        huData.targetId = game.gameSeats[game.turn].userId;
        huData.isQiangGangHu = game.lastGangPai == pai;
    }
    //连庄
    huData.zhuangCount = game.winCount;
    //花
    huData.huas = seat.huas.length;
    //杠牌
    huData.gangs = seat.angangs.length*2 + seat.diangangs.length + seat.bugangs.length;

    socketMgr.notify_all_user_operate(seat, MJ_HU_ACTION, pai);

    // moveToUser(game, seat.seatIndex);

    willEndGame(game, false, huData);
};

function willEndGame(game, force, huData) {
    if (huData) {
        calcScore(game, huData);
    }

    logger.debug('numOfGames', game.numOfGames);
    if (game.numOfGames == game.room.conf.jushu || force) {
        //强制结束时 可能一局游戏也没开始
        if (force) setupGameStatistics(game);
        endGame(game);
    } else {
        var resultInfo = sortResult(game);
        socketMgr.notify_game_result(game, resultInfo);
    }
};

function guo(userId) {
    var seat = gamesSeats[userId];
    if (seat == null || !hasOperations(seat)) return -1;

    //这里还要处理过胡的情况
    var game = seat.game;
    //是自己摸牌 且能胡 那就是自摸
    if (game.turn == seat.seatIndex) {
        doGuo(game);
        return;
    }
    //不是自己出牌 且能胡 算过胡
    if (seat.seatIndex != game.turn && seat.canHu) {
        seat.lastGuoHu = game.chuPai;
    }
    //只有一家能胡
    if (game.mayActionLength == 1) {
        doGuo(game);
        return;
    }

    if (game.didActionArr == null) game.didActionArr = [];
    game.didActionArr.push(seat.userId);
    seat.didAction = MJ_GUO_ACTION;

    //所有人都做了动作
    if (game.didActionArr.length == game.mayActionLength) {
        doAction(game);
    }
};

function doGuo(game) {
    clearAllOptions(game);
    var lastSeat = game.gameSeats[game.turn];
    lastSeat.folds.push(game.chuPai);

    //过了之后 只能去摸牌
    moveToNextUser(game);
    toMoPai(game);
};

function doAction(game) {
    //必定是能进行多个动作 才到这一步
    var seat = null;
    var turn = game.turn;
    logger.debug('doAction game turn', game.turn);
    //0    1 2 3    4    1 2 3
    //2    3 0 1    2    3 4 1
    //3    0 1 2    3    4 1 2
    //可以胡牌的话 先算谁胡牌
    var length = game.mayHuArr.length;
    for (var i = 0; i < length; i++) {
        turn ++;
        turn %=4;
        var uid = game.mayHuArr[i];
        var tempSeat = gamesSeats[uid];
        if (turn == tempSeat.seatIndex && seat.didAction == MJ_HU_ACTION) {
            seat = tempSeat;
            break;
        }
    }
    if (seat) {
        doHu(seat);
        return;
    }

    //碰 和 吃的情况 不碰 && 吃
    //只可能一个人吃 一个人碰
    var pengSeat = gamesSeats[game.mayPeng];
    var chiSeat = gamesSeats[game.mayChi];
    if (game.mayPeng && pengSeat.didAction != MJ_GUO_ACTION) {
        if (pengSeat.didAction == MJ_PNEG_ACTION) {
            doPeng(pengSeat);
        } else if (seat.didAction == MJ_GANG_DIAN_ACTION){
            doGang(pengSeat);
        }
    } else if (game.mayChi && chiSeat.didAction != MJ_GUO_ACTION) {
        doChi(chiSeat);
    } else {
        doGuo(game);
    }
};

function spliceHolds(seat, pai) {
    var index = seat.holds.indexOf(pai);
    seat.holds.splice(index,1);
    seat.countMap[pai] --;
    if (seat.countMap[pai] == 0) {
        delete seat.countMap[pai];
    }
};

function clearAllOptions(game){
    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];
        seat.canPeng = false;
        seat.canGang = false;
        seat.canChi = false;
        seat.canHu = false;
        seat.didAction = -1;
    }
};

function setupGame(userId, roomNo) {
    if (!roomMgr.isCreator(roomNo, userId)) return;
    var room = roomMgr.getRoomByRoomNo(roomNo);
    if (room == null) {
        logger.error("begin error", roomNo);
        return;
    }
    var game = games[roomNo];
    if (game == null) {
        initGame(room);
    }
};

//开始游戏
function beginGame(userId, roomNo) {
    if (!roomMgr.isCreator(roomNo, userId)) return;
    var room = roomMgr.getRoomByRoomNo(roomNo);
    if (Object.keys(room.userInfos).length < room.conf.renshu) {
        logger.error("plays num error", roomNo);
        return;
    }
    var game = games[roomNo];
    if (game.memberOfReady < room.conf.renshu) {
        logger.error("ready num error", roomNo);
        return;
    }

    refreshGame(game);
    xiPai(game);
    faPai(game);
};

//准备
function ready(userId, roomNo) {
    var game = games[roomNo];
    logger.debug('ready numOfGames', game.numOfGames);
    if (game.numOfGames == 0) { //玩家正在坐下阶段 numOfGames == 0
        var room = roomMgr.getRoomByRoomNo(roomNo);
        var userInfo = room.userInfos[userId];
        userInfo.ready = true;
        game.memberOfReady++;

        var seat = game.gameSeats[userInfo.index];
        seat.userId = userInfo.userId;
        seat.ready = true;
        gamesSeats[seat.userId] = seat;
        return;
    }
    //如果所有人都准备完成 更改游戏计数器
    var seat = gamesSeats[userId];
    if (!seat.ready) {
        seat.ready = true;
        game.memberOfReady++;
    }
    if (game.memberOfReady == game.plays) {
        refreshGame(game);
        xiPai(game);
        faPai(game);
    }
};

//统计当局结果信息
function sortResult(game) {
    var resultInfo = {};
    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];
        var info = {
            score: seat.score,
            holds: seat.holds,
        };
        resultInfo[seat.userId] = info;
    }
    return resultInfo;
};

//算积分
function calcScore(game, huData) {
    game.state = PERIOD_END;

    setupGameStatistics(game);
    var statistics = game.room.statistics;

    var score = huData.fan;
    score += huData.gangs;
    score += huData.huas;
    if (huData.isGangShangHu) {
        score = score*2;
    } else if (huData.isQiangGangHu) {
        score = score + 10;
    }
    score = score*huData.zhuangCount;

    var winUserId = huData.userId;
    var shuUserId = huData.isZimo ? null : huData.targetId;
    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];
        var info = statistics[seat.userId];

        info.dianGang += seat.diangangs.length;
        info.anGang += seat.angangs.length;
        if (huData.isZimo) {
            if (seat.userId == winUserId) {
                info.ziMo += 1;
                seat.score = score*game.plays;
                info.score += seat.score;
            } else {
                info.dianPao += 1;
                seat.score = -score;
                info.score += seat.score;
            }
        } else {
            if (seat.userId == winUserId) {
                info.jiePao += 1;
                seat.score = score;
                info.score += seat.score;
            } else if (seat.userId == shuUserId){
                info.dianPao += 1;
                seat.score = -score;
                info.score += seat.score;
            }
        }
        game.gameSeats[i] = seat;
    }
    redis.cacheRoomGameResult(game.roomNo, JSON.stringify(game.room.statistics));
};

function setupGameStatistics(game) {
    var statistics = game.room.statistics;
    if (Object.keys(statistics).length < 1) {
        for (var i = 0; i < game.plays; i++) {
            var seat = game.gameSeats[i];
            var info = statistics[seat.userId];
            if (info == null) {
                info = {
                    dianGang: 0,
                    anGang: 0,
                    ziMo: 0,
                    dianPao: 0,
                    jiePao: 0,
                    score: 0,
                };
                statistics[seat.userId] = info;
            }
        }
    }
};

//结束游戏
function endGame(game){
    //保存房间信息
    roomMgr.saveRoomInfo(game.roomNo);
    socketMgr.notify_game_end_statistics(game);
    //关闭房间
    roomMgr.closeRoom(game.room.creator, game.room.roomNo);
    //清楚房间信息
    delGameInfo(game);

    //延迟2S 做断开socket 清理room操作
    setTimeout(function () {
        //踢出所有房间socket
        socketMgr.knockout_all(game);
    }, 1000);
};

//清除游戏信息
function delGameInfo(game) {
    for (var i = 0; i < game.plays; i++) {
        var seat = game.gameSeats[i];
        delete gamesSeats[seat.userId];
    }
    delete games[game.roomNo];
    delete times[game.roomNo];
};

//解散请求处理
function dissolveRequest(userId, operate) {
    var seat = gamesSeats[userId];
    if (seat.dissoveOperate != -1) return;

    var game = seat.game;
    if (operate == 1) { //同意
        game.memberOfAgree++;
    } else if (operate == 2){ //拒绝
        game.memberOfRefuse++;
    }
    seat.dissoveOperate = operate;

    //所有人都操作了
    if ((game.memberOfAgree + game.memberOfRefuse) == game.plays) {
        handleDissolveResult(game, true);
    } else {
        startDissolveTime(game);
    }
};

function startDissolveTime(game) {
    if (times[game.roomNo]) return;
    var timeout = setTimeout(function () {
        for (var i = 0; i < game.plays; i++) {
            var seat = game.gameSeats[i];
            if (seat.dissoveOperate == -1) {
                seat.dissoveOperate = 1;
                game.memberOfAgree++;
            }
        }
        handleDissolveResult(game, false);
    }, 90000);//90s后
    times[game.roomNo] = timeout;
};

function handleDissolveResult(game, clear) {
    if (game.memberOfAgree == game.plays) {
        //所有人都同意
        socketMgr.notify_dissolve_result(game, 1);
        //强制结束游戏
        willEndGame(game, true);
    } else {
        socketMgr.notify_dissolve_result(game, 0);
    }
    if (clear) {
        clearTimeout(times[game.roomNo]);
    }
    delete times[game.roomNo];
};

//游戏类型
function gameState(roomNo) {
    var game = games[roomNo];
    if (game == null) return -1;
    return game.state;
};

//获取game对象
function getGameByRoomNo(roomNo) {
    return games[roomNo];
};

//MJ牌类型
function getMJType(pai){
    //9*4*3=108张 东南西北中发白7*4*3=84张 梅兰竹菊春夏秋冬8*1
    if(pai > 0 && pai < 10){//万 1 2 3 4 5 6 7 8 9
        return 0;
    } else if(pai > 10 && pai < 20){//条 11 12 13 14 15 16 17 18 19
        return 1;
    } else if(pai > 20 && pai < 30){//筒 21 22 23 24 25 26 27 28 29
        return 2;
    } else if(pai > 30 && pai < 40) {//东南西北中发白
        return 3;
    } else if(pai > 40 && pai < 50) {//梅兰竹菊春夏秋冬
        return 4;
    }
};

exports.getGameByRoomNo = getGameByRoomNo;

exports.ready = ready;
exports.beginGame = beginGame;
exports.setupGame = setupGame;
exports.willEndGame = willEndGame;
exports.dissolveRequest = dissolveRequest;
exports.gameState = gameState;

exports.chuPai = chuPai;
exports.chi = chi;
exports.peng = peng;
exports.gang = gang;
exports.hu = hu;
exports.guo = guo;




















